﻿using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Input;
using Aeon.Configuration;
using Aeon.Emulator.DebugSupport;
using Aeon.Presentation.Dialogs;
using Microsoft.Win32;
using Aeon.Emulator.Dos.VirtualFileSystem;

namespace Aeon.Emulator.Launcher
{
    /// <summary>
    /// Top-level Aeon Emulator window.
    /// </summary>
    public partial class MainWindow : Window
    {
        #region Public Static Fields
        /// <summary>
        /// The Map Drives command.
        /// </summary>
        public static readonly ICommand MapDrivesCommand = new RoutedCommand("MapDrives", typeof(MainWindow));
        /// <summary>
        /// The Show Instruction Log command.
        /// </summary>
        public static readonly ICommand ShowInstructionLogCommand = new RoutedCommand("ShowInstructionLog", typeof(MainWindow));
        #endregion

        #region Private Fields
        private PerformanceWindow performanceWindow;
        private VirtualMachineConfiguration currentConfig;
        private InstructionLog log;
        private bool hasActivated;
        private System.Windows.Media.Imaging.GifBitmapEncoder gifEncoder = new System.Windows.Media.Imaging.GifBitmapEncoder();
        private System.Windows.Threading.DispatcherTimer timer;
        private PaletteDialog paletteWindow;
        #endregion

        #region Constructors
        /// <summary>
        /// Initializes a new instance of the MainWindow class.
        /// </summary>
        public MainWindow()
        {
            InitializeComponent();
        }
        #endregion

        #region Protected Methods
        /// <summary>
        /// Invoked when the window is activated.
        /// </summary>
        /// <param name="e">Unused EventArgs instance.</param>
        protected override void OnActivated(EventArgs e)
        {
            base.OnActivated(e);

            if(!this.hasActivated)
            {
                var args = App.Current.Args;

                if(args.Count > 0)
                    QuickLaunch(args[0]);

                this.hasActivated = true;
            }

            if(emulatorDisplay != null && this.WindowState != WindowState.Minimized)
                emulatorDisplay.Focus();
        }
        #endregion

        #region Private Methods
        /// <summary>
        /// Applies a configuration to a VirtualMachine instance.
        /// </summary>
        /// <param name="p">Specifies the VirtualMachine instance and device creation parameters.</param>
        private void ApplyConfiguration(VirtualMachineConfiguration config)
        {
            this.emulatorDisplay.ResetEmulator();
            var p = new CreateDeviceParameters(this.emulatorDisplay.EmulatorHost.VirtualMachine, new System.Windows.Interop.WindowInteropHelper(System.Windows.Application.Current.MainWindow).Handle);

            foreach(var drive in config.Drives)
            {
                var vmDrive = p.VirtualMachine.FileSystem.Drives[drive.DriveLetter];
                vmDrive.DriveType = drive.DriveType;
                vmDrive.Mapping = drive.DriveConfiguration.Create();
                vmDrive.HasCommandInterpreter = drive.DriveType == DriveType.Fixed;
            }

            this.emulatorDisplay.EmulatorHost.VirtualMachine.FileSystem.WorkingDirectory = new VirtualPath(config.StartupPath);

            foreach(var device in config.Devices.Where(d => d.IsEnabled))
                p.VirtualMachine.RegisterVirtualDevice(device.Create(p));

            emulatorDisplay.MouseInputMode = config.IsMouseAbsolute ? Presentation.MouseInputMode.Absolute : Presentation.MouseInputMode.Relative;
            emulatorDisplay.EmulationSpeed = config.EmulationSpeed / 5;
            toolBar.Visibility = config.HideUserInterface ? Visibility.Collapsed : Visibility.Visible;
            mainMenu.Visibility = config.HideUserInterface ? Visibility.Collapsed : Visibility.Visible;
            if(!string.IsNullOrEmpty(config.Title))
                this.Title = config.Title;
        }
        /// <summary>
        /// Remounts all of the drives on the current VirtualMachine instance.
        /// </summary>
        /// <param name="drives">Drive configurations to mount.</param>
        private void RemountDrives(MountedDriveCollection drives)
        {
            var fileSystem = this.emulatorDisplay.EmulatorHost.VirtualMachine.FileSystem;
            foreach(var drive in fileSystem.Drives)
            {
                drive.DriveType = DriveType.None;
                drive.Mapping = null;
            }

            foreach(var driveConfig in drives)
            {
                var drive = fileSystem.Drives[driveConfig.DriveLetter];
                drive.DriveType = driveConfig.DriveType;
                drive.Mapping = driveConfig.DriveConfiguration.Create();
            }
        }
        /// <summary>
        /// Launches the current Aeon configuration.
        /// </summary>
        private void LaunchCurrentConfig()
        {
            ApplyConfiguration(this.currentConfig);
            if(!string.IsNullOrEmpty(this.currentConfig.Launch))
            {
                var launchTargets = this.currentConfig.Launch.Split(new char[] { ' ', '\t' }, 2, StringSplitOptions.RemoveEmptyEntries);
                if(launchTargets.Length == 1)
                    this.emulatorDisplay.EmulatorHost.LoadProgram(launchTargets[0]);
                else
                    this.emulatorDisplay.EmulatorHost.LoadProgram(launchTargets[0], launchTargets[1]);
            }
            else
                this.emulatorDisplay.EmulatorHost.LoadProgram("COMMAND.COM");

            this.emulatorDisplay.EmulatorHost.Run();
        }
        /// <summary>
        /// Launches a DOS program or a configuration file.
        /// </summary>
        /// <param name="fileName">Name of file to launch.</param>
        private void QuickLaunch(string fileName)
        {
            if(string.Equals(Path.GetExtension(fileName), ".AeonConfig", StringComparison.InvariantCultureIgnoreCase))
                this.currentConfig = VirtualMachineConfiguration.Load(fileName, LauncherEnvironment.ConfigurationMapper);
            else
                this.currentConfig = LauncherEnvironment.GetQuickLaunchConfiguration(System.IO.Path.GetDirectoryName(fileName), System.IO.Path.GetFileName(fileName));

            LaunchCurrentConfig();
        }
        /// <summary>
        /// Displays a task dialog.
        /// </summary>
        /// <param name="title">Title of the dialog window.</param>
        /// <param name="caption">Caption text of the dialog window.</param>
        /// <param name="items">Task dialog items to display.</param>
        /// <returns>Selected task dialog item or null if dialog was closed.</returns>
        private TaskDialogItem ShowTaskDialog(string title, string caption, params TaskDialogItem[] items)
        {
            var taskDialog = new TaskDialog() { Owner = this, Items = items, Icon = this.Icon, Title = title, Caption = caption };
            if(taskDialog.ShowDialog() == true)
                return taskDialog.SelectedItem;
            else
                return null;
        }
        /// <summary>
        /// Toggles whether the instruction log is enabled.
        /// </summary>
        private void ToggleInstructionLog()
        {
            if(this.log == null)
                this.log = new InstructionLog(1000);
            else
                this.log = null;

            this.enableLogMenuItem.IsChecked = this.log != null;
            this.emulatorDisplay.SetInstructionLog(this.log);
        }
        private void StartRecordingGif()
        {
            timer = new System.Windows.Threading.DispatcherTimer(TimeSpan.FromSeconds(0.1), System.Windows.Threading.DispatcherPriority.Normal, SaveGifFrame, this.Dispatcher);
            timer.Start();
        }
        private void SaveGifFrame(object sender, EventArgs e)
        {
            var frame = System.Windows.Media.Imaging.BitmapFrame.Create(this.emulatorDisplay.DisplayBitmap.Clone());
            this.gifEncoder.Frames.Add(frame);
        }
        private void StopRecordingGif()
        {
            timer.Stop();
            using(var stream = File.Create(@"C:\Code\Test.gif"))
            {
                this.gifEncoder.Save(stream);
            }

            this.gifEncoder.Frames.Clear();
        }
        #endregion

        #region Event Handlers
        private void NewSession_Click(object sender, RoutedEventArgs e)
        {
            var configEditor = new ConfigEditor()
            {
                Configuration = LauncherEnvironment.ConfigurationMapper.CreateDefaultConfiguration(),
                Owner = this
            };

            configEditor.Configuration.StartupPath = @"C:\";

            try
            {
                if(configEditor.ShowDialog() == true)
                {
                    this.currentConfig = configEditor.Configuration;
                    LaunchCurrentConfig();
                }
            }
            catch(FileNotFoundException ex)
            {
                MessageBox.Show(string.Format("Launch target {0} not found.", ex.FileName), "Aeon", MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }
        private void QuickLaunch_Click(object sender, RoutedEventArgs e)
        {
            OpenFileDialog fileDialog = new OpenFileDialog();
            fileDialog.Filter = "Programs (*.exe,*.com;*.AeonConfig)|*.exe;*.com;*.AeonConfig|All files (*.*)|*.*";
            fileDialog.Title = "Run DOS program...";
            if(fileDialog.ShowDialog(this) == true)
                QuickLaunch(fileDialog.FileName);
        }
        private void CommandPrompt_Click(object sender, RoutedEventArgs e)
        {
            Aeon.Presentation.Browsers.FolderBrowserDialog dialog = new Presentation.Browsers.FolderBrowserDialog();
            dialog.Title = "Select folder for C:\\ drive...";
            if(dialog.ShowDialog(this))
            {
                this.currentConfig = LauncherEnvironment.GetQuickLaunchConfiguration(dialog.Path);
                LaunchCurrentConfig();
            }
        }
        private void Close_CanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = true;
        }
        private void Close_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            Close();
        }
        private void MapDrives_CanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            if(this.emulatorDisplay != null)
            {
                var state = this.emulatorDisplay.EmulatorState;
                e.CanExecute = state == EmulatorState.Running || state == EmulatorState.Paused;
            }
        }
        private void MapDrives_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            if(emulatorDisplay.EmulatorState != EmulatorState.Running && emulatorDisplay.EmulatorState != EmulatorState.Paused)
                return;

            bool wasRunning = emulatorDisplay.EmulatorState == EmulatorState.Running;

            if(wasRunning)
                this.emulatorDisplay.EmulatorHost.Pause();

            var newConfig = this.currentConfig.Clone();

            var mounter = new MountWindow()
            {
                Owner = this,
                Drives = newConfig.Drives
            };

            if(mounter.ShowDialog() == true)
            {
                this.currentConfig = newConfig;
                RemountDrives(this.currentConfig.Drives);
            }

            if(wasRunning)
                this.emulatorDisplay.EmulatorHost.Run();
        }
        private void emulatorDisplay_EmulatorStateChanged(object sender, RoutedEventArgs e)
        {
            CommandManager.InvalidateRequerySuggested();
            if(this.emulatorDisplay.EmulatorState == EmulatorState.ProgramExited && this.currentConfig != null && this.currentConfig.HideUserInterface)
                Close();
        }
        private void About_Click(object sender, RoutedEventArgs e)
        {
            var about = new AboutWindow();
            about.Owner = this;
            about.ShowDialog();
        }
        private void slowerButton_Click(object sender, RoutedEventArgs e)
        {
            if(emulatorDisplay != null)
            {
                int newSpeed = Math.Max(EmulatorHost.MinimumSpeed, emulatorDisplay.EmulationSpeed - (EmulatorHost.MaximumSpeed / 10));
                if(newSpeed != emulatorDisplay.EmulationSpeed)
                    emulatorDisplay.EmulationSpeed = newSpeed;
            }
        }
        private void fasterButton_Click(object sender, RoutedEventArgs e)
        {
            if(emulatorDisplay != null)
            {
                int newSpeed = Math.Min(EmulatorHost.MaximumSpeed, emulatorDisplay.EmulationSpeed + (EmulatorHost.MaximumSpeed / 10));
                if(newSpeed != emulatorDisplay.EmulationSpeed)
                    emulatorDisplay.EmulationSpeed = newSpeed;
            }
        }
        private void Copy_CanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            if(emulatorDisplay != null && emulatorDisplay.DisplayBitmap != null)
                e.CanExecute = true;
        }
        private void Copy_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            if(emulatorDisplay != null)
            {
                var bmp = emulatorDisplay.DisplayBitmap;
                if(bmp != null)
                    Clipboard.SetImage(bmp);
            }
        }
        private void emulatorDisplay_EmulationError(object sender, Presentation.EmulationErrorRoutedEventArgs e)
        {
            var end = new TaskDialogItem("End Program", "Terminates the current emulation session.");
            var debug = new TaskDialogItem("Debug", "View the current emulation session in the Aeon debugger.");
            var logging = new TaskDialogItem("Restart with Logging", "Restarts the current session with CPU instruction logging enabled.");

            TaskDialogItem selection;

            if(this.log == null)
                selection = ShowTaskDialog("Emulation Error", "An error occurred which caused the emulator to halt: "+ e.Message + " What would you like to do?", end, debug, logging);
            else
                selection = ShowTaskDialog("Emulation Error", "An error occurred which caused the emulator to halt: " + e.Message + " What would you like to do?", end, debug);

            if(selection == end || selection == null)
                emulatorDisplay.ResetEmulator();
            else if(selection == logging)
            {
                ToggleInstructionLog();
                LaunchCurrentConfig();
            }
            else if(selection == debug)
            {
                var debuggerWindow = new DebuggerWindow() { Owner = this, EmulatorHost = this.emulatorDisplay.EmulatorHost, InstructionLog = this.log };
                debuggerWindow.Show();
                debuggerWindow.UpdateDebugger();
            }
        }
        private void emulatorDisplay_CurrentProcessChanged(object sender, RoutedEventArgs e)
        {
            if(this.currentConfig == null || string.IsNullOrEmpty(this.currentConfig.Title))
            {
                var process = emulatorDisplay.CurrentProcess;
                if(process != null)
                    this.Title = string.Format("{0} - Aeon", process);
                else
                    this.Title = "Aeon";
            }
        }
        private void Website_Click(object sender, RoutedEventArgs e)
        {
            Process.Start("http://aeonemulator.net");
        }
        private void CheckForUpdate_Click(object sender, RoutedEventArgs e)
        {
            Version latestVersion = null;

            try
            {
                using(var wc = new WebClient())
                {
                    var version = wc.DownloadString("http://aeonemulator.net/Download/LatestVersion");
                    if(!string.IsNullOrEmpty(version))
                        latestVersion = Version.Parse(version);
                }
            }
            catch
            {
                MessageBox.Show(this, "Unable to contact server.", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
            }

            if(typeof(MainWindow).Assembly.GetName().Version < latestVersion)
            {
                if(MessageBox.Show(this, "There is a newer version of Aeon available for download. Would you like to get it now?", "Aeon", MessageBoxButton.YesNo, MessageBoxImage.Information) == MessageBoxResult.Yes)
                    Process.Start("http://aeonemulator.net/Download");
            }
            else
                MessageBox.Show(this, "You have the latest version.", "Aeon", MessageBoxButton.OK, MessageBoxImage.Information);
        }
        private void PerformanceWindow_Click(object sender, RoutedEventArgs e)
        {
            if(performanceWindow != null)
                performanceWindow.Activate();
            else
            {
                performanceWindow = new PerformanceWindow();
                performanceWindow.Closed += performanceWindow_Closed;
                performanceWindow.Owner = this;
                performanceWindow.EmulatorDisplay = emulatorDisplay;
                performanceWindow.Show();
            }
        }
        private void performanceWindow_Closed(object sender, EventArgs e)
        {
            if(performanceWindow != null)
            {
                performanceWindow.Closed -= performanceWindow_Closed;
                performanceWindow = null;
            }
        }
        private void EnableInstructionLog_Click(object sender, RoutedEventArgs e)
        {
            ToggleInstructionLog();
        }
        private void ShowInstructionLog_CanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            if(this.emulatorDisplay != null)
                e.CanExecute = this.emulatorDisplay.EmulatorState != EmulatorState.Running && this.log != null;
        }
        private void ShowInstructionLog_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            var logWindow = new Presentation.Debugger.InstructionLogWindow() { Owner = this, InstructionLog = this.log, Icon = this.Icon };
            logWindow.ShowDialog();
        }
        private void ShowDebugger_Click(object sender, RoutedEventArgs e)
        {
            var debuggerWindow = new DebuggerWindow() { Owner = this, EmulatorHost = this.emulatorDisplay.EmulatorHost, InstructionLog = this.log };
            debuggerWindow.Show();
            debuggerWindow.UpdateDebugger();
        }
        private void ShowPalette_Click(object sender, RoutedEventArgs e)
        {
            if(this.paletteWindow != null)
                this.paletteWindow.Activate();
            else
            {
                this.paletteWindow = new PaletteDialog() { Owner = this, EmulatorDisplay = this.emulatorDisplay, Icon = this.Icon };
                this.paletteWindow.Closed += PaletteWindow_Closed;
                paletteWindow.Show();
            }
        }
        private void PaletteWindow_Closed(object sender, EventArgs e)
        {
            if(this.paletteWindow != null)
            {
                this.paletteWindow.Closed -= this.PaletteWindow_Closed;
                this.paletteWindow = null;
            }
        }
        private void ShowAudioBuffer_Click(object sender, RoutedEventArgs e)
        {
            var blaster = this.emulatorDisplay.EmulatorHost.VirtualMachine.Devices.OfType<Sound.Blaster.SoundBlaster>().First();
            var audioWindow = new AudioBufferWindow() { Owner = this, Icon = this.Icon };
            audioWindow.Attach(blaster.GetBufferPositions, blaster.BufferSize);
            audioWindow.Show();
        }
        #endregion

        private void MenuItem_Click(object sender, RoutedEventArgs e)
        {
            StartRecordingGif();
        }

        private void MenuItem_Click_1(object sender, RoutedEventArgs e)
        {
            StopRecordingGif();
        }

        private void instructionProfilerMenuItem_Click(object sender, RoutedEventArgs e)
        {
            var window = new InstructionProfilerWindow()
            {
                Owner = this
            };

            window.Show();
        }
    }
}
